/*
 * These files are distributed under the terms of the GNU GPLv2, the GNU General
 * Public version 2. See LICENSE.txt
 */
package fileCommodore.plugin;

import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.prefs.*;

import javax.swing.*;

import org.apache.commons.vfs.*;

import fileCommodore.*;
import fileCommodore.model.*;

/**
 * @author nado18@gmail.com, on Jul 26, 2007
 */
public abstract class Plugin
{
	public abstract void handle( KeyEvent ev );
	
	public abstract void activeSelectionChanged();
	
	public abstract void inactiveSelectionChanged();
	
	private Selection activeSelection;
	private Selection inactiveSelection;
	
	private static Main frame;
	
	public static void setController( Main m )
	{
		frame = m;
	}
	
	public void show(JPopupMenu menu)
	{
		getController().show(menu);
	}
	
	public Main getController()
	{
		return frame;
	}
	
	public void setActiveSelection( Selection selection )
	{
		activeSelection = selection;
	}
	
	public void setInactiveSelection( Selection selection )
	{
		inactiveSelection = selection;
	}
	
	public Selection getActiveSelection()
	{
		return activeSelection;
	}
	
	public Selection getInactiveSelection()
	{
		return inactiveSelection;
	}
	
	private String getKey( String key )
	{
		String retval =
				String.format( "plugin-%s-%s", getClass().getName(), key );
		
		return retval;
	}
	
	public String getProperty( String key, String defaultValue )
	{
		Preferences p = Preferences.userNodeForPackage( Main.class );
		String pluginKey = getKey( key );
		String retval = p.get( pluginKey, defaultValue );
		return retval;
	}
	
	public String getProperty( String key )
	{
		return getProperty( key, "" );
	}
	
	public void setProperty( String key, String value )
	{
		Preferences p = Preferences.userNodeForPackage( Main.class );
		String pluginKey = getKey( key );
		p.put( pluginKey, value );
	}
	
	public void log( Exception ex )
	{
		
	}
	
	protected JFrame getDialogParent()
	{
		return frame;
	}
	
	public void report( String msg, Exception ex )
	{
		String pluginName = getClass().getName();
		
		showError( String.format(
			"There was an error in the '%s' plugin.\r\n\r\n%s\r\n\r\n%s",
			pluginName,
			msg,
			ex ), String.format( "Error in '%s' plugin.", pluginName ) );
	}
	
	private static boolean isMac()
	{
		return Main.isMac();
	}
	
	private static boolean isWindows()
	{
		return Main.isWindows();
	}
	
	private static boolean isUnix()
	{
		return Main.isUnix();
	}
	
	public boolean isAltDown( KeyEvent ev )
	{
		boolean retval =
				( isMac() && ev.isMetaDown() ) || ( !isMac() && ev.isAltDown() );
		
		return retval;
	}
	
	public boolean isCtrlDown( KeyEvent ev )
	{
		boolean retval = ev.isControlDown();
		
		return retval;
	}
	
	private static Process launch( File workingDirectory, Collection<String> command )
			throws IOException
	{
		// Read the OS' environment variables
		Vector<String> vEnv = new Vector<String>();
		Map<String, String> mEnv = System.getenv();
		String[] env = new String[] {};
		for (String key : mEnv.keySet())
		{
			String value = mEnv.get( key );
			vEnv.add( String.format( "%s=%s", key, value ) );
		}
		env = vEnv.toArray( env );
		
		String[] sCmd = new String[] {};
		sCmd = command.toArray( sCmd );
		
		Process p = Runtime.getRuntime().exec( sCmd, env, workingDirectory );
		return p;
	}
	
	public static Process launchProcessWithoutTerminal( File workingDirectory,
		String[] command ) throws IOException
	{
		Vector<String> vCmd = new Vector<String>();
		if( isWindows() )
		{
			vCmd.add( "cmd" );
			vCmd.add( "/c" );
			vCmd.add( "start" );
		}
		else if( isMac() )
		{
			vCmd.add( "open" );
		}
		else
		{
			vCmd.add( "open" );
		}
		
		for (String arg : command)
		{
			vCmd.add( arg );
		}
		
		Process p = launch( workingDirectory, vCmd );
		return p;
	}
	
	public Process launchProcessWithTerminal( File workingDirectory,
		String[] command, boolean keepOpen ) throws IOException
	{
		Vector<String> vCmd = new Vector<String>();
		
		if( isWindows() )
		{
			String toggle = keepOpen ? "/k" : "/c";
			vCmd.add( "cmd" );
			vCmd.add( "/c" );
			vCmd.add( "start" );
			vCmd.add( "cmd" );
			vCmd.add( toggle );
			
			for (String arg : command)
			{
				vCmd.add( arg );
			}
		}
		else if( isMac() )
		{
			vCmd.add("osascript");
			vCmd.add("-e");
			
			StringBuffer sb = new StringBuffer("tell app \"Terminal\" to do script \"");
			for(int i=0; i<command.length; i++)
			{
				if( i > 0 )
				{
					sb.append(" ");
				}
				sb.append(command[i]);
			}
			sb.append("\"");
			
			vCmd.add( sb.toString() );
		}
		else 
		{
			throw new UnsupportedOperationException(
				"Don't know how to launch applications with terminal on this platform."
			);
		}
		
		Process p = launch( workingDirectory, vCmd );
		return p;
	}
	
	public Process launchTerminal( File workingDirectory ) throws IOException
	{
		Vector<String> vCmd = new Vector<String>();
		if( isWindows() )
		{
			vCmd.add( "cmd" );
			vCmd.add( "/c" );
			vCmd.add( "start" );
			vCmd.add( "cmd" );
		}
		else if( isMac() )
		{
			vCmd.add("open");
			vCmd.add("/Applications/Utilities/Terminal.app");
		}
		else
		{
			vCmd.add("xterm");
		}
		
		Process p = launch( workingDirectory, vCmd );
		return p;
	}
	
	public void showError( String title, String message )
	{
		show( title, message, JOptionPane.ERROR_MESSAGE );
	}
	
	public void showError( String title, String message, Exception ex )
	{
		String msg = String.format( "%s\r\n\r\n%s", message, ex );
		showError( title, msg );
	}
	
	public void showWarning( String title, String message )
	{
		show( title, message, JOptionPane.WARNING_MESSAGE );
	}
	
	private void show( String title, String message, int messageType )
	{
		JOptionPane.showMessageDialog(
			getDialogParent(),
			message,
			title,
			messageType );
	}
	
	public void changeActiveTo( FileObject fo ) throws FileSystemException
	{
		frame.changeActiveTo( fo );
	}
	
	public void changeActiveTo( String path ) throws FileSystemException
	{
		FileObject fo = resolve( path );
		changeActiveTo( fo );
	}
	
	public void changeInactiveTo( FileObject fo ) throws FileSystemException
	{
		frame.changeInactiveTo( fo );
	}
	
	public void changeInactiveTo( String path ) throws FileSystemException
	{
		FileObject fo = resolve( path );
		changeInactiveTo( fo );
	}
	
	private FileObject resolve( String path ) throws FileSystemException
	{
		FileObject retval = frame.resolve( path );
		return retval;
	}
	
	public File toFile( FileObject fo )
	// TODO: Compare with FileRowItem.toFile
	{
		String sUri = fo.getName().getPath();
		File retval = new File( sUri );
		return retval;
	}
	
	public String ask( String title, String message )
	{
		String retval =
				JOptionPane.showInputDialog(
					frame,
					message,
					title,
					JOptionPane.QUESTION_MESSAGE );
		return retval;
	}

	// Used by wildcard launcher
	private String pathOf(FileRowItem fri) {
		String retval = fri.toFile().getAbsolutePath();
		return retval;
	}

	// Used by wildcard launcher
	private Process launch(File working, String[] args, boolean useShell) {
		Process result = null;
		
		try
		{
			if (useShell)
			{
				result = launchProcessWithTerminal( working, args, true );
			}
			else
			{
				result = launchProcessWithoutTerminal( working, args );
			}
		}
		catch (IOException e)
		{
			report( "Could not open DOS shell.", e );
		}
		
		return result;
	}

	// Used by wildcard launcher
	protected Vector<Process> substituteAndLaunch(File startup, String[] in, boolean useShell) {
		ArrayList<String> al = new ArrayList<String>();
		ArrayList<Boolean> subed = new ArrayList<Boolean>();
		for (String s : in)
		{
			al.add( s );
			subed.add( false );
		}
		
		String s = pathOf( getActiveSelection().getSelected() );
		String d = pathOf( getActiveSelection().getCurrent() );
		String n = getActiveSelection().getSelected().getFileName();
		
		String S = pathOf( getInactiveSelection().getSelected() );
		String D = pathOf( getInactiveSelection().getCurrent() );
		String N = getInactiveSelection().getSelected().getFileName();
		
		boolean has_t = false;
		boolean has_T = false;
		
		// Substitute %s, %S, %d and %D and check for @@t and @@T
		for (int i = 0; i < al.size(); i++)
		{
			subed.set( i, true );
			String v = al.get( i );
			if ("%s".equals( v ))
			{
				al.set( i, s );
			}
			else if ("%S".equals( v ))
			{
				al.set( i, S );
			}
			else if ("%d".equals( v ))
			{
				al.set( i, d );
			}
			else if ("%D".equals( v ))
			{
				al.set( i, D );
			}
			else if ("%n".equals( v ))
			{
				al.set( i, n );
			}
			else if ("%N".equals( v ))
			{
				al.set( i, N );
			}
			else
			{
				if ("@t".equals( v ))
				{
					has_t = true;
				}
				else if ("@T".equals( v ))
				{
					has_T = true;
				}
				subed.set( i, false );
			}
		}
		
		// Substitute %t and %T
		for (int i = al.size() - 1; i >= 0; i--)
		// Done backwards to avoid shuffling indices as we insert elements
		{
			if (!subed.get( i ))
			{
				Vector<FileRowItem> tagged = null;
				String v = al.get( i );
				
				if ("%t".equals( v ))
				{
					tagged = getActiveSelection().getTagged();
				}
				else if ("%T".equals( v ))
				{
					
					tagged = getInactiveSelection().getTagged();
				}
				
				if (null != tagged)
				{
					al.remove( i );
					subed.remove( i );
					
					// We'll always insert at the head
					Collections.reverse( tagged );
					
					for (FileRowItem fri : tagged)
					{
						al.add( i, pathOf( fri ) );
						subed.add( i, true );
					}
				}
			}
		}
		
		String[] args = new String[] {};
		args = al.toArray( args );
		Vector<Process> result = new Vector<Process>();
		
		if (has_t && has_T)
		{
			for (FileRowItem t : getActiveSelection().getTagged())
			{
				for (FileRowItem T : getInactiveSelection().getTagged())
				{
					String[] tmpArgs = subTag( t, T, args, subed );
					Process process = launch( startup, tmpArgs, useShell );
					result.add(process);
				}
			}
		}
		else if (has_t)
		{
			for (FileRowItem t : getActiveSelection().getTagged())
			{
				String[] tmpArgs = subTag( t, null, args, subed );
				Process process = launch( startup, tmpArgs, useShell );
				result.add(process);
			}
		}
		else if (has_T)
		{
			for (FileRowItem T : getInactiveSelection().getTagged())
			{
				String[] tmpArgs = subTag( null, T, args, subed );
				Process process = launch( startup, tmpArgs, useShell );
				result.add(process);
			}
		}
		else
		{
			Process process = launch( startup, args, useShell );
			result.add(process);
		}
		
		return result;
	}

	// Used by wildcard launcher
	private String[] subTag(FileRowItem t, FileRowItem T, String[] in, ArrayList<Boolean> subed) {
		String[] retval = new String[in.length];
		
		for (int i = 0; i < in.length; i++)
		{
			if ("@t".equals( in[i] ) && !subed.get( i ))
			{
				retval[i] = pathOf( t );
			}
			else if ("@T".equals( in[i] ) && !subed.get( i ))
			{
				retval[i] = pathOf( T );
			}
			else
			{
				retval[i] = in[i];
			}
		}
		
		return retval;
	}
	
}
